package com.xhj.mybatis.executor;

import com.xhj.mybatis.cache.CacheKey;
import com.xhj.mybatis.cache.impl.PerpetualCache;
import com.xhj.mybatis.mapping.BoundSql;
import com.xhj.mybatis.mapping.MappedStatement;
import com.xhj.mybatis.mapping.ParameterMapping;
import com.xhj.mybatis.reflection.MetaObject;
import com.xhj.mybatis.session.Configuration;
import com.xhj.mybatis.session.LocalCacheScope;
import com.xhj.mybatis.session.ResultHandler;
import com.xhj.mybatis.session.RowBounds;
import com.xhj.mybatis.transaction.Transaction;
import com.xhj.mybatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import static com.xhj.mybatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

/**
 * BaseExecutor
 *
 * @author XJks
 * @description 执行器的基础实现类，提供了事务管理和资源关闭等通用功能
 */
public abstract class BaseExecutor implements Executor {
    /**
     * 日志对象，用于记录日志信息
     */
    private Logger logger = LoggerFactory.getLogger(BaseExecutor.class);

    /**
     * 事务对象，管理数据库事务
     */
    protected Transaction transaction;

    /**
     * 包装器，用于装饰器模式扩展 Executor 功能
     */
    protected Executor wrapper;

    /**
     * TODO:本地缓存，一级缓存，存储查询结果
     */
    protected PerpetualCache localCache;

    /**
     * 配置对象，保存全局配置信息
     */
    protected Configuration configuration;

    /**
     * 查询调用栈深度，用于管理嵌套查询
     */
    protected int queryStack;

    /**
     * 是否关闭标志，表示执行器是否已经被关闭
     */
    private boolean closed;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.configuration = configuration;
        this.transaction = transaction;
        // TODO:初始化包装器为当前对象，之后可能会调用 setExecutorWrapper 方法对其进行修改
        this.wrapper = this;
        // 初始化一级缓存，实现类为 PerpetualCache
        this.localCache = new PerpetualCache("LocalCache");
    }

    /**
     * 获取当前事务对象
     */
    @Override
    public Transaction getTransaction() {
        if (closed) {
            // 已关闭则抛异常
            throw new RuntimeException("Executor was closed.");
        }
        return transaction;
    }

    /**
     * 关闭执行器，参数 forceRollback 表示是否强制回滚
     */
    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                // 回滚事务
                rollback(forceRollback);
            } finally {
                if (transaction != null) {
                    // 关闭事务
                    transaction.close();
                }
            }
        } catch (SQLException e) {
            // 忽略异常，无法处理
            logger.warn("Unexpected exception on closing transaction.  Cause: " + e);
        } finally {
            // 释放资源，标记为关闭
            transaction = null;
            closed = true;
        }
    }

    /**
     * 提交事务
     */
    @Override
    public void commit(boolean required) throws SQLException {
        if (closed) {
            throw new RuntimeException("Cannot commit, transaction is already closed");
        }
        // TODO:提交事务前，清空本地缓存
        clearLocalCache();
        if (required) {
            // 提交事务
            transaction.commit();
        }
    }

    /**
     * 回滚事务
     */
    @Override
    public void rollback(boolean required) throws SQLException {
        if (!closed) {
            try {
                // TODO:回滚事务前，清空本地缓存
                clearLocalCache();
                // todo:源码这里存在刷新批处理语句的逻辑，待完善。
            } finally {
                if (required) {
                    // 回滚事务
                    transaction.rollback();
                }
            }
        }
    }

    /**
     * 执行更新操作（insert、update、delete）
     */
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        if (closed) {
            throw new RuntimeException("Executor was closed.");
        }
        // TODO:执行更新操作前，清空本地缓存
        clearLocalCache();
        // 调用子类实现的更新方法
        return doUpdate(ms, parameter);
    }

    /**
     * 查询方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取绑定的 SQL 对象
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 创建缓存 key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        /**
         * TODO:
         *  1.下方代码是调用主查询方法
         *  2.在插件机制下的分析:
         *      2.1 该方法 {@link #query(MappedStatement, Object, RowBounds, ResultHandler)} 是只能被外部类 {@link com.xhj.mybatis.session.defaults.DefaultSqlSession} 调用的，外部类调用该方法时，使用的是包装后的执行器对象（插件增强后的执行器对象），是代理对象，所以该方法会被拦截器链处理。
         *      2.2 但是该方法内部调用的主查询方法 {@link #query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)} 是在当前类中直接调用的，并不是通过包装器调用的，所以该方法调用时使用的仍然是当前类的原始对象，并不是代理对象，所以该方法不会被拦截器链处理。
         *      2.3 也就是说，插件只能拦截外部调用的查询方法，无法拦截执行器内部调用的查询方法。
         *  3.因此，如果需要拦截执行器的查询操作，插件只能拦截外部调用的查询方法，无法拦截执行器内部调用的查询方法。
         *  4.这也是 MyBatis 插件机制的一个限制点，用户在使用插件时需要注意这一点。
         *  5.总之，插件机制无法拦截执行器内部调用的查询方法，这是设计上的一个限制。
         */
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    /**
     * 查询方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        if (closed) {
            throw new RuntimeException("Executor was closed.");
        }
        // 如果是最外层查询且需要刷新缓存，则清空本地缓存
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        // 存储查询结果的列表
        List<E> list;
        try {
            // 查询栈加一
            queryStack++;
            // 如果没有结果处理器，则尝试从一级缓存获取结果
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            // 根据缓存结果决定是否需要从数据库查询
            if (list != null) {
                // todo:源码这里存在处理存储过程输出参数的逻辑，待完善。
            } else {
                // 从数据库查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // 查询栈减一
            queryStack--;
        }
        // 查询栈为 0 时，处理延迟加载
        if (queryStack == 0) {
            // todo:源码这里存在处理延迟加载的逻辑，待完善。
            // 如果本地缓存作用域为 STATEMENT，则清空本地缓存
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                clearLocalCache();
            }
        }
        return list;
    }

    /**
     * 从数据库查询数据，并放入缓存
     */
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 声明结果列表
        List<E> list;
        /**
         * TODO:
         *  1.{@link ExecutionPlaceholder.EXECUTION_PLACEHOLDER} 是一个查询标记，这个占位符可以避免在查询缓存时出现“脏读”。
         *  2.当多个线程同时查询同一个 key 的缓存，其中一个线程正在执行查询，而其他线程还没有获取到缓存结果时就直接返回了缓存中的占位符。
         *  3.因为查询需要与数据库交互通信，有一定的开销延迟，添加这个占位符可以让其他线程知道该查询正在进行中，从而避免重复查询数据库，提高性能。
         *  4.相关问题分析:
         *      4.1 线程 A 在执行 {@link #queryFromDatabase} 该方法时，执行到了下方语句 {@code localCache.putObject(key, EXECUTION_PLACEHOLDER)} 时，但没执行到 {@code list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql)} 语句。
         *      4.2 与此同时，线程 B 也在执行查询操作，执行到了 {@link #query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)} 方法中的语句 {@code list = resultHandler == null ? (List<E>) localCache.getObject(key) : null} 语句，此时将 EXECUTION_PLACEHOLDER 强转成 List<E> 时，会发生异常，从而达到了告知线程 B 该查询正在进行中，避免重复查询数据库。
         */
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 查询数据库
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 移除占位符，避免影响后续查询
            localCache.removeObject(key);
        }
        // 放入缓存
        localCache.putObject(key, list);
        // todo:源码这里存在处理存储过程输出参数的逻辑，待完善。
        return list;
    }

    /**
     * 创建一个缓存键，用于唯一标识某个查询结果，便于缓存管理
     */
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        // 如果执行器已关闭，则抛出异常
        if (closed) {
            throw new RuntimeException("Executor was closed.");
        }
        // 新建一个缓存 key 对象
        CacheKey cacheKey = new CacheKey();
        // 将 SQL 语句的 id 加入缓存 key
        cacheKey.update(ms.getId());
        // 将分页的偏移量（从第几条开始）加入缓存 key
        cacheKey.update(rowBounds.getOffset());
        // 将分页的大小（每页多少条）加入缓存 key
        cacheKey.update(rowBounds.getLimit());
        // 将 SQL 语句的文本内容加入缓存 key
        cacheKey.update(boundSql.getSql());
        // 获取 SQL 参数的映射列表
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        // 获取类型处理器注册表，用于判断参数类型
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // 遍历所有参数映射
        for (ParameterMapping parameterMapping : parameterMappings) {
            /**
             * todo:
             *  1.源码这里还会判断 parameterMapping 的模式是否为 OUT，如果是 OUT 类型则不处理。
             *  2.OUT 类型是存储过程的输出参数，这里不参与缓存 key 计算，所以可以忽略。
             *  3.因为目前我们还没有实现存储过程相关功能，这里直接处理，无需判断。
             */
            // 用于存储参数值
            Object value;
            // 获取参数名
            String propertyName = parameterMapping.getProperty();
            // 根据不同情况获取参数值
            if (boundSql.hasAdditionalParameter(propertyName)) {
                // TODO:如果 BoundSql 中有额外参数（如 foreach、bind 等动态参数），优先取额外参数的值
                value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
                // 如果参数对象为 null，则参数值为 null
                value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                // 如果参数对象是简单类型（如 int、String），直接作为参数值
                value = parameterObject;
            } else {
                // 否则参数对象是复杂类型（如 JavaBean），通过反射获取属性值
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
            }
            // 将参数值加入缓存 key
            cacheKey.update(value);
        }
        // 如果配置中有环境信息（如多数据源），将环境 id 加入缓存 key，避免不同环境下 key 冲突
        if (configuration.getEnvironment() != null) {
            cacheKey.update(configuration.getEnvironment().getId());
        }
        // 返回生成好的缓存 key
        return cacheKey;
    }

    /**
     * 判断指定 key 是否已缓存
     */
    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return localCache.getObject(key) != null;
    }

    /**
     * 清空本地缓存
     */
    @Override
    public void clearLocalCache() {
        if (!closed) {
            localCache.clear();
        }
    }

    /**
     * 设置执行器的包装器
     */
    @Override
    public void setExecutorWrapper(Executor wrapper) {
        this.wrapper = wrapper;
    }

    /**
     * 关闭 Statement，释放数据库资源
     *
     * @param statement 要关闭的 Statement 对象
     */
    protected void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                // 忽略异常
            }
        }
    }

    /**
     * 抽象方法，子类实现具体的更新逻辑
     */
    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

    /**
     * 抽象方法，子类实现查询逻辑
     */
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
}